/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/
#pragma once

struct kStructDataView;

#include "kdata.h"
#include "kerror.h"

struct kStructDataView {
public:
	const uint8_t* ptr;
	size_t itemSize;
	size_t itemCount;

	inline kStructDataView() {
		ptr = 0;
		itemSize = 0;
		itemCount = 0;
	}
	inline kStructDataView(const uint8_t* ptr, size_t itemSize, size_t itemCount) {
		this->ptr = ptr;
		this->itemSize = itemSize;
		this->itemCount = itemCount;
	}
	template<typename T> inline kStructDataView(const T* ptr, size_t itemCount) {
		this->ptr = reinterpret_cast<const uint8_t*>(ptr);
		this->itemSize = sizeof(T);
		this->itemCount = itemCount;
	}
	template<typename T> inline kStructDataView(const std::basic_string<T>& str) {
		this->ptr = reinterpret_cast<const uint8_t*>(str.data());
		this->itemSize = sizeof(T);
		this->itemCount = str.size();
	}
	template<typename T, size_t N> inline kStructDataView(const T (&ptr)[N]) {
		this->ptr = reinterpret_cast<const uint8_t*>(ptr);
		this->itemSize = sizeof(T);
		this->itemCount = N;
	}
	inline operator kDataView() const {
		return kDataView(ptr, itemSize*itemCount);
	}
	inline size_t getSize() const {
		return itemSize*itemCount;
	}
	inline const uint8_t* getItemPtr(size_t index) const {
		return ptr + index*itemSize;
	}
	template<typename T> inline const T& item(size_t index) const {
		if (index >= itemCount)
			throw kError(_T("Index outside of range!"));
		return *reinterpret_cast<const T*>(getItemPtr(index));
	}
	template<typename T> inline const T& field(size_t index, size_t offset) const {
		if (index >= itemCount)
			throw kError(_T("Index outside of range!"));
		return *reinterpret_cast<const T*>(getItemPtr(index) + offset);
	}
	template<typename T> inline const T& fieldInc(size_t index, size_t& offset) const {
		const T& result = field<T>(index, offset);
		offset += sizeof(T);
		return result;
	}
	inline kDataView item(size_t index) const {
		return kDataView(ptr + index*itemSize, itemSize);
	}
};

struct kStructData {
	uint8_t* ptr;
	size_t itemSize;
	size_t itemCount;

	inline kStructData() {
		ptr = 0;
		itemSize = 0;
		itemCount = 0;
	}
	inline kStructData(uint8_t* ptr, size_t itemSize, size_t itemCount) {
		this->ptr = ptr;
		this->itemSize = itemSize;
		this->itemCount = itemCount;
	}
	template<typename T> inline kStructData(T* ptr, size_t itemCount) {
		this->ptr = reinterpret_cast<uint8_t*>(ptr);
		this->itemSize = sizeof(T);
		this->itemCount = itemCount;
	}
	template<typename T, size_t N> inline kStructData(T (&ptr)[N]) {
		this->ptr = reinterpret_cast<uint8_t*>(ptr);
		this->itemSize = sizeof(T);
		this->itemCount = N;
	}
	inline operator kStructDataView() const {
		return kStructDataView(ptr, itemSize, itemCount);
	}
	inline operator kDataView() const {
		return kDataView(ptr, itemSize*itemCount);
	}
	inline operator kData() const {
		return kData(ptr, itemSize*itemCount);
	}
	inline size_t getSize() const {
		return itemSize*itemCount;
	}
	inline uint8_t* getItemPtr(size_t index) const {
		return ptr + index*itemSize;
	}
	template<typename T> inline T& item(size_t index) const {
		if (index >= itemCount)
			throw kError(_T("Index outside of range!"));
		return *reinterpret_cast<T*>(getItemPtr(index));
	}
	template<typename T> inline T& field(size_t index, size_t offset) const {
		if (index >= itemCount)
			throw kError(_T("Index outside of range!"));
		return *reinterpret_cast<T*>(getItemPtr(index) + offset);
	}
	template<typename T> inline T& fieldInc(size_t index, size_t& offset) const {
		T& result = field<T>(index, offset);
		offset += sizeof(T);
		return result;
	}
	inline kData item(size_t index) const {
		return kData(ptr + index*itemSize, itemSize);
	}
};

class kStructDataBufferView : public kBase {
protected:
	kStructData _data;

	inline kStructDataBufferView() : _data() {
	}

public:
	kStructDataView getStructDataView() const {
		return _data;
	}
	virtual ~kStructDataBufferView();

	inline const uint8_t* getPtr() const {
		return _data.ptr;
	}
	inline size_t getItemSize() const {
		return _data.itemSize;
	}
	inline size_t getItemCount() const {
		return _data.itemCount;
	}
	inline size_t getSize() const {
		return _data.getSize();
	}
	inline const uint8_t* getItemPtr(size_t index) const {
		return _data.getItemPtr(index);
	}
	template<typename T> inline const T& item(size_t index) const {
		return _data.item<T>(index);
	}
	template<typename T> inline const T& field(size_t index, size_t offset) const {
		return _data.field<T>(index, offset);
	}
	template<typename T> inline const T& fieldInc(size_t index, size_t& offset) const {
		return _data.fieldInc<T>(index, offset);
	}
	inline kDataView item(size_t index) const {
		return _data.item(index);
	}
};

class kStructDataBuffer : public kStructDataBufferView {
public:
	kStructDataBuffer();
	kStructDataBuffer(size_t itemSize, size_t itemCount);
	kStructDataBuffer(const kStructDataView& source);
	kStructDataBuffer(size_t itemSize, kStream* stream);
	virtual ~kStructDataBuffer();

	void resize(size_t itemCount);
	void fill(const uint8_t* value, size_t index = 0, size_t count = -1) const;
	inline void resizeFill(size_t itemCount, const uint8_t* value) {
		size_t oldcount = _data.itemCount;
		resize(itemCount);
		if (itemCount > oldcount) {
			fill(value, oldcount, itemCount - oldcount);
		}
	}
	void append(const kStructDataView& data);
	void replace(const kStructDataView& data);
	void replace(size_t itemSize, size_t itemCount);
	template<typename T> inline void fillT(const T& value, size_t index = 0, size_t count = -1) const {
		fill(reinterpret_cast<const uint8_t*>(&value), index, count);
	}
	template<typename T> inline void resizeFillT(size_t itemCount, const T& value) const {
		resizeFill(itemCount, reinterpret_cast<const uint8_t*>(&value));
	}

	const kStructData& getStructData() const {
		return _data;
	}
	inline uint8_t* getPtr() const {
		return _data.ptr;
	}
	inline uint8_t* getItemPtr(size_t index) const {
		return _data.getItemPtr(index);
	}
	template<typename T> inline T& item(size_t index) const {
		return _data.item<T>(index);
	}
	template<typename T> inline T& field(size_t index, size_t offset) const {
		return _data.field<T>(index, offset);
	}
	template<typename T> inline T& fieldInc(size_t index, size_t& offset) const {
		return _data.fieldInc<T>(index, offset);
	}
	inline kData item(size_t index) const {
		return _data.item(index);
	}
};
